package login

import (
	"fmt"

	"strings"
	"time"

	Log "github.com/cihub/seelog"

	"gitee.com/tomatomeatman/golang-repository/bricks3/function/configutil"
	"gitee.com/tomatomeatman/golang-repository/bricks3/function/data/md5util"
	"gitee.com/tomatomeatman/golang-repository/bricks3/function/filecache"
	"gitee.com/tomatomeatman/golang-repository/bricks3/model/globalvariable"
	"gitee.com/tomatomeatman/golang-repository/bricks3/utils/sqlfactory"
	"github.com/gin-gonic/gin"
)

type LoginServer struct {
}

var (
	sessionTimeout int64                = 600 //超时时间(秒)
	appMd5Key      string                     //访问密钥
	loginCache     *filecache.FileCache       //登录缓存
)

/**
 * 初始化
 */
func init() {
	globalvariable.Add("Module@LoginServer", &LoginServer{})

	sessionTimeout = configutil.ReadConfigKey("", "App", "SessionTimeout", sessionTimeout).(int64)
	appMd5Key = configutil.ReadConfigKey("", "App", "Md5Key", "8888").(string)

	go func() {
		loginCache, _ = filecache.GetCheck("./temp/cache/Login/LoginCache.gob", sessionTimeout, true)
	}()
}

/**
 * 登录
 * @param sNameOrNo
 * @param sPass
 * @param owner 用户来源表
 * @param iDevice 设备类型,1:PC,2:手机,3:平板,4.....
 * @param iResultInfo 是否返回用户信息
 * @return
 */
func (ls LoginServer) In(ctx *gin.Context, sNameOrNo, sPass string, iDevice, iResultInfo int) (bool, string, interface{}) {
	if strings.TrimSpace(sNameOrNo) == "" {
		return false, "登录名不能为空！", 8000
	}

	if strings.TrimSpace(sPass) == "" {
		return false, "密码不能为空！", 8001
	}

	bl, _, res := ls.FindByNameOrNo(sNameOrNo)
	if !bl {
		return false, "查询失败，请稍后重试！", 8002
	}

	list := res.([]*LoginUser)

	if len(list) < 1 {
		return false, "相关登录名的用户不存在！", 8003
	}

	if len(list) != 1 {
		return false, "存在重名用户，请使用工号登录！", 8004
	}

	loginUser := list[0]
	if md5util.Lower(sPass, appMd5Key) != loginUser.Pass {
		Log.Debug("用户“", loginUser.Name, "”登录时密码错误")
		return false, "用户密码错误！", 8005
	}

	if fmt.Sprintf("%v", loginUser.State) != GetLoginBusinessInfo().StateValue {
		return false, "登录受限，请联系管理员！", 8006
	}

	if iDevice < 1 {
		iDevice = 1
	}

	id := loginUser.Id

	//LoginLogService{}.AddLog(ctx, id, sNameOrNo)//AOP已经实现
	//moduleutil.DoMethod("LoginLogService", "AddLog", ctx, id, sNameOrNo) //直接调用

	//--剔除旧的登录用户--//
	key := fmt.Sprintf("login_id:%s:%d", id, iDevice)
	if _, bl := loginCache.Del(key); bl {
		Log.Debug("旧登录用户'", id, "'因同账号登录被剔除登录集合!")
	}

	token := md5util.Lower(id, "|", time.Now().Unix(), "|", iDevice, "|", appMd5Key)
	loginUser.Token = token //分配的Token
	loginUser.Device = iDevice
	loginUser.LastDate = time.Now().Unix()

	// 存储新登录记录
	loginCache.Set(loginUser, token, key)

	if iResultInfo != 1 {
		return true, "登录成功", token
	}

	result := loginUser.Clone()
	result.Pass = "" //必须清除

	return true, "登录成功", result
}

/**
 * 登出
 * @param token
 * @return
 */
func (ls LoginServer) Out(token string) (bool, string, interface{}) {
	if strings.TrimSpace(token) == "" {
		return false, "令牌参数缺失", 8002
	}

	item, bl := loginCache.Del(token)
	if item != nil {
		Log.Debug("登录用户'", item.(LoginUser).Id, "'主动进行了登出操作!")
	}

	if !bl && item != nil {
		Log.Warn("登出失败")
		return false, "登出失败", 8008
	}

	return true, "登出成功", 8999
}

/**
 * 判断token是否已经登录
 * @param token 令牌
 * @return
 */
func (ls LoginServer) Check(token string) (bool, string, interface{}) {
	if strings.TrimSpace(token) == "" {
		return false, "令牌参数缺失", 8002
	}

	if bl := loginCache.HasKey(token, true); !bl {
		return false, "该登录令牌信息失效", 8001
	}

	return true, "该登录令牌信息有效,并延长有效期", 8999
}

/**
 * 登录心跳操作,token存在则更新并返回true,没有则返回false
 * @param token 令牌
 * @return
 */
func (ls LoginServer) Heartbeat(token string) (bool, string, interface{}) {
	if strings.TrimSpace(token) == "" {
		return false, "令牌参数缺失", 8001
	}

	if bl := loginCache.HasKey(token, true); !bl {
		return false, "令牌已失效,请重新登录", 8002
	}

	return true, "令牌心跳已经更新", 8999
}

/**
 * 取登录信息
 * @param token 令牌
 * @return
 */
func (ls LoginServer) GetLogin(token string) (bool, string, interface{}) {
	if strings.TrimSpace(token) == "" {
		return false, "令牌参数缺失", 8001
	}

	var loginUser *LoginUser
	_, bl := loginCache.Get(token, &loginUser)
	if !bl {
		return false, "令牌已失效,请重新登录", 8002
	}

	result := loginUser.Clone()
	result.Pass = "" //必须清除

	return true, "找到令牌对应的登录信息", result
}

/**
 * 取登录信息Map格式
 * @param token 令牌
 * @return
 */
func (ls LoginServer) GetLoginMap(token string) (bool, string, interface{}) {
	if strings.TrimSpace(token) == "" {
		return false, "令牌参数缺失", 8001
	}

	var loginUser *LoginUser
	_, bl := loginCache.Get(token, &loginUser)
	if !bl {
		return false, "令牌已失效,请重新登录", 8002
	}

	result := loginUser.Clone()
	result.Pass = "" //必须清除

	m := map[string]interface{}{
		"sId":       result.Id,
		"sName":     result.Name,
		"sNo":       result.No,
		"sType":     result.Type,
		"iState":    result.State,
		"dLastDate": result.LastDate,
		"token":     result.Token,
		"iDevice":   result.Device,
		"attached":  result.Attached,
	}

	return true, "找到令牌对应的登录信息", m
}

/**
 * 根据用户和密码取对应的用户编号
 * @param sNameOrNo
 * @param sPass
 * @return
 */
func (ls LoginServer) GetUserId(sNameOrNo, sPass string) (bool, string, interface{}) {
	if strings.TrimSpace(sNameOrNo) == "" {
		return false, "登录名不能为空！", 8000
	}

	if strings.TrimSpace(sPass) == "" {
		return false, "密码不能为空！", 8001
	}

	bl, _, res := ls.FindByNameOrNo(sNameOrNo)
	if !bl {
		return false, "查询失败，请稍后重试！", 8002
	}

	list := res.([]*LoginUser)

	if len(list) < 1 {
		return false, "相关登录名的用户不存在！", 8003
	}

	if len(list) != 1 {
		return false, "存在重名用户，请使用工号登录！", 8004
	}

	loginUser := list[0]
	if md5util.Lower(sPass, appMd5Key) == loginUser.Pass {
		Log.Debug("用户“", loginUser.Name, "”登录时密码错误")
		return false, "用户密码错误！", 8005
	}

	if fmt.Sprintf("%v", loginUser.State) != GetLoginBusinessInfo().StateValue {
		return false, "登录受限，请联系管理员！", 8006
	}

	return true, "查询成功", loginUser.Id
}

// 取当前用户编号
func CurrentLoginUserId(ctx *gin.Context) string {
	//-- 取头参数 --//
	header := ctx.Request.Header.Get("token")

	bl, _, res := LoginServer{}.GetLogin(header)
	if !bl {
		return ""
	}

	loginUser := res.(*LoginUser)
	return loginUser.Id
}

// 取当前用户编号
func CurrentLoginUser(ctx *gin.Context) (bool, *LoginUser) {
	//-- 取头参数 --//
	header := ctx.Request.Header.Get("token")

	bl, _, res := LoginServer{}.GetLogin(header)
	if !bl {
		return false, nil
	}

	return true, res.(*LoginUser)
}

/**
 * 取用户名或工号对应的用户集合
 * @param sNameOrNo
 * @return
 */
func (ls LoginServer) FindByNameOrNo(sNameOrNo string) (bool, string, interface{}) {
	info := GetLoginBusinessInfo()

	var build strings.Builder
	build.WriteString("SELECT ")
	build.WriteString(info.Id)
	build.WriteString(" AS sId, ")
	build.WriteString(info.Name)
	build.WriteString(" AS sName, ")
	build.WriteString(info.Pass)
	build.WriteString(" AS sPass, ")
	build.WriteString(info.State)
	build.WriteString(" AS iState, ")
	build.WriteString(info.Type)
	build.WriteString(" AS sType, ")
	build.WriteString(info.No)
	build.WriteString(" AS sNo FROM ")
	build.WriteString(info.DbName)
	build.WriteString(".")
	build.WriteString(info.TableName)
	build.WriteString(" WHERE ")
	build.WriteString(info.No)
	build.WriteString(" = @sNameOrNo")
	build.WriteString(" UNION ")
	build.WriteString("SELECT ")
	build.WriteString(info.Id)
	build.WriteString(" AS sId, ")
	build.WriteString(info.Name)
	build.WriteString(" AS sName, ")
	build.WriteString(info.Pass)
	build.WriteString(" AS sPass, ")
	build.WriteString(info.State)
	build.WriteString(" AS iState, ")
	build.WriteString(info.Type)
	build.WriteString(" AS sType, ")
	build.WriteString(info.No)
	build.WriteString(" AS sNo FROM ")
	build.WriteString(info.TableName)
	build.WriteString(" WHERE ")
	build.WriteString(info.Name)
	build.WriteString(" = @sNameOrNo")

	where := map[string]interface{}{
		"sNameOrNo": sNameOrNo,
	}

	var result []*LoginUser
	bl, msg, res := sqlfactory.Find(info.DbName, &result, build.String(), where)
	if !bl {
		return false, msg, res
	}

	return true, "查询成功", result
}
